意外と盲点?Lambda関数にトリガーを追加するときはリソースベースポリシーを意識しよう
コンバンハ、千葉(幸)です。
Lambda関数には、トリガーをセットすることができます。付随してリソースベースのポリシーの設定が必要となるのですが、マネジメントコンソールからセットする際には裏でよしなにやってくれるので、意識する機会は減りがちです。CloudFormationなど非コンソール操作で作成する際には、明示的にリソースベースポリシーの指定が必要なので注意しましょう。
目次
- 目次
- 先に結論
- LambdaをCloudWatch イベントで定期実行させる設定をマネジメントコンソールで実施する
- CloudFormationで作成するパターンを考える
- Ref関数とGetAtt関数が返してくれるもの
- 終わりに
先に結論
- Lambda関数にはリソースベースポリシーがある
- Lambda関数にトリガーをセットする場合、トリガーからのInvocateを許可するポリシーを定義する必要がある
- CloudFormationでリソースベースポリシーを設定するときはタイプ
AWS::Lambda::Permission
を用いる
ちなみに
このリソースに、リソースベースのポリシーってあったっけ?とわからなくなったときは、このページから一覧で確認できるのでブックマークオススメです。
LambdaをCloudWatch イベントで定期実行させる設定をマネジメントコンソールで実施する
トリガーのセットをマネジメントコンソールで作業する際を考えてみましょう。
前回こんな記事を書きました。Lambda + 実行用のIAMロールまでCloudFormation化して作成してあるので、これをCloudWatch イベントで定期実行させてみます。
なお、ここで作成されるLambdaの名称は「LogGroupPutRetentionPolicy
」としています。
CloudWatch イベントルールの作成
Lambdaコンソール側からの操作でも追加可能ですが、今回はCloudWatch イベントのコンソールから作成していきます。
「CloudWatch」→「イベント」→「ルール」の画面から「ルールの作成」を押下し、以下を入力していきます。
- イベントソース:今回はスケジュールベースで5分ごとに実行させます
- ターゲット:作成済みのLambda関数を指定します
ルールの名称を決めて作成します。今回はtest-rule
としました。
ここでこのようなメッセージが表示されます。これがまさにターゲットのLambda関数のリソースベースのポリシーを編集することを指しています。皆さんは意識できていましたか?
CloudWatch Events はターゲットに必要な権限を追加し、このルールがトリガーされたときに呼び出せるようにします。
Lambda側での確認
ターゲットに指定したLambdaの詳細画面から確認すると、トリガーに先ほどのCloudWatch Eventルールが表示されていることがわかります。 (この画面から「トリガーを追加」を選択してCloudWatch イベントルールを作成することもできます。)
ここの表示は何を基に描画されているかというと、Lambda関数の[アクセス権限]タブの...
[リソースベースのポリシー]という部分と連動しています。
先ほどCloudWatch イベントルールをマネジメントコンソールから作成した際に、自動的にポリシーが編集されています。アクションはlambda:InvokeFunction
で、リソースとコンディションを絞ることで必要最小限の権限付与で納まるようになっています。
{ "Version": "2012-10-17", "Id": "default", "Statement": [ { "Sid": "AWSEvents_test-rule_Id2091584616251", "Effect": "Allow", "Principal": { "Service": "events.amazonaws.com" }, "Action": "lambda:InvokeFunction", "Resource": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:LogGroupPutRetentionPolicy", "Condition": { "ArnLike": { "AWS:SourceArn": "arn:aws:events:ap-northeast-1:xxxxxxxxxxxx:rule/test-rule" } } } ] }
このリソースベースのポリシーはマネジメントコンソールからは編集できません。
CloudFormationで作成するパターンを考える
上記の構成をCloudFormationで作成する場合を考えましょう。
コードは前回記事時点で作成済みの以下に追記する形で考えます。
折り畳み
AWSTemplateFormatVersion: '2010-09-09' Description: "Create Lambda" # ------------------------------------------------------------# # Input Parameters # ------------------------------------------------------------# Metadata: AWS::CloudFormation::Interface: ParameterGroups: Parameters: - LambdaMemorySize - LambdaTimeout - LogGroupPutRetentionDays Parameters: LambdaMemorySize: Description: 128 ~ 3008. The value must be a multiple of 64. Type: String Default: 128 LambdaTimeout: Description: The maximum allowed value is 900 seconds. Type: String Default: 60 LogGroupPutRetentionDays: Description: Possible values are 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, and 3653. Type: String Default: 60 Resources: # ------------------------------------------------------------# # IAM Role # ------------------------------------------------------------# LambdaFunctionRole: Type: "AWS::IAM::Role" Properties: RoleName: "Lambda-LogGroupPutRetentionPolicy-Role" AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: "lambda.amazonaws.com" Action: "sts:AssumeRole" Path: "/" ManagedPolicyArns: - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess # ------------------------------------------------------------# # Lambda # ------------------------------------------------------------# LambdaFunction: Type: "AWS::Lambda::Function" Properties: FunctionName: "LogGroupPutRetentionPolicy" Handler: "index.lambda_handler" Role: !GetAtt [ LambdaFunctionRole, Arn ] Environment: Variables: RetentionDays : !Ref LogGroupPutRetentionDays MemorySize: !Ref LambdaMemorySize Timeout: !Ref LambdaTimeout Code: ZipFile: | import boto3 import os Days=os.environ['RetentionDays'] # Possible values are: 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, and 3653. def lambda_handler(event, context): logs_client = boto3.client('logs') response = logs_client.describe_log_groups() group_list = response['logGroups'] while 'nextToken' in response: response = logs_client.describe_log_groups(nextToken=response['nextToken']) add_groups = response['logGroups'] group_list.extend(add_groups) for log_group in group_list: print(log_group['logGroupName']) result = logs_client.put_retention_policy( logGroupName=log_group['logGroupName'], retentionInDays=int(Days) ) code = result['ResponseMetadata']['HTTPStatusCode'] print('HTTPStatusCode is ' + str(code)) Runtime: "python3.7"
Lambdaは論理IDLambdaFunction
を持つリソースとして定義しています。
CloudWatch Eventルールの追加
リファレンスを参考に、以下のセクションを追加しました。
# ------------------------------------------------------------# # CloudWatchEvent # ------------------------------------------------------------# LambdaScheduleEvent: Type: AWS::Events::Rule Properties: Description: schedule event for lambda ScheduleExpression: 'rate(5 minutes)' Name: test-rule State: ENABLED Targets: - Arn: !GetAtt LambdaFunction.Arn Id: !Ref LambdaFunction
当初は上記のリソースだけ追加すればトリガーの設定が完了していると勘違いしていました。この状態だとLambda関数は一向に実行されず、CloudWatch EventルールのメトリクスでFailedInvocations
が記録され続けることになりました。
CloudWatch Eventルール側では定期的に呼び出しを実行するものの、Lambda関数のリソースベースポリシーで許可されていないために、そこでブロックされているという状態です。
AWS::Lambda::Permissionでのリソースベースポリシーの追加
LambdaのリソースベースポリシーもCloudFormationのテンプレートで定義してあげる必要があるので、こちらもリファレンスを参考にコードを追加します。上で確認したリソースベースポリシーを再現するだけであれば、以下のような書き方で充足しています。
# ------------------------------------------------------------# # LambdaPermission # ------------------------------------------------------------# LambdaPermission: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !GetAtt LambdaFunction.Arn Principal: events.amazonaws.com SourceArn: !GetAtt LambdaScheduleEvent.Arn
こちらを追記することで、マネジメントコンソールからの作業で実施したものと同等の構成を作成することができます。("SID"のセクションは省略されていますが。)
{ "Version": "2012-10-17", "Id": "default", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "events.amazonaws.com" }, "Action": "lambda:InvokeFunction", "Resource": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:LogGroupPutRetentionPolicy", "Condition": { "ArnLike": { "AWS:SourceArn": "arn:aws:events:ap-northeast-1:xxxxxxxxxxxx:rule/test-rule" } } } ] }
上記のコードの組み合わせによって、マネジメントコンソールでセットした場合と同一の構成を実現できます。
Ref関数とGetAtt関数が返してくれるもの
コードを試行錯誤する中でようやく調べ方を理解したので書いておきます。
CloudFormationのリファレンスの中で、リソースタイプごとに戻り値が記載されています。(戻り値の記載が無いリソースタイプもあるので、それはRefやGetAttの対象に指定できないということです。)
例えばタイプAWS::Lambda::Functionのリソースであれば、以下で確認できます。
Ref関数(参照番号
と訳されていますが)ではLambda関数のリソース名を、GettAtt関数ではArn
属性を指定することでLambda関数のARNを返してくれることが分かります。
終わりに
Lambdaのリソースベースポリシーについて意識が必要であることを学びました。
リソースベースポリシーの有名どころではS3のバケットポリシーやIAMロールの信頼関係ポリシーがありますが、アクションが正しく実行されるためにはIAMポリシー(アイデンティティベースポリシー)との組み合わせを意識してあげる必要があります。いい感じに設定してくれるマネジメントコンソールはとても便利ですが、裏側の仕組みも抑えておきましょう。
以上、足先が冷えがちな千葉(幸)がお届けしました。